/**
 * Copyright 2013 Netflix, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.netflix.astyanax.cql.reads;

import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicReference;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.datastax.driver.core.BoundStatement;
import com.datastax.driver.core.PreparedStatement;
import com.datastax.driver.core.RegularStatement;
import com.datastax.driver.core.Session;

/**
 * Template for {@link PreparedStatement} caching for a query Q. 
 * The class provides the basic functionality to store a cached reference to the PreparedStatement 
 * that is generated by the extending class. Hence actual logic for constructing the PrepatedStatement
 * and binding values to that statement is not defined here. That must be provided by the extending classes.  
 * 
 * @author poberai
 *
 * @param <Q>
 */
public abstract class QueryGenCache<Q> {
	
	private static final Logger LOG = LoggerFactory.getLogger(QueryGenCache.class);

	// reference to the session object. This is required for "preparing" a statement
	private AtomicReference<Session> sessionRef = new AtomicReference<Session>(null); 
	// The cached reference to the query constructed by extending classes
	private final AtomicReference<PreparedStatement> cachedStatement = new AtomicReference<PreparedStatement>(null);

	/**
	 * Constructor
	 * @param sessionR
	 */
	public QueryGenCache(AtomicReference<Session> sessionR) {
		this.sessionRef = sessionR;
	}

	/**
	 * Get the bound statement from the prepared statement
	 * @param query
	 * @param useCaching
	 * @return BoundStatement
	 */
	public BoundStatement getBoundStatement(Q query, boolean useCaching) {

		PreparedStatement pStatement = getPreparedStatement(query, useCaching);
		return bindValues(pStatement, query);
	}

	/**
	 * Get the bound statemnent by either constructing the query or using the cached statement underneath.
	 * Note that the caller can provide useCaching as a knob to turn caching ON/OFF. 
	 * If false, then the query is just constructed using the extending class and returned. 
	 * If true, then the cached reference is consulted. If the cache is empty, then the query is constructed
	 * and used to seed the cache. 
	 * 
	 * @param query
	 * @param useCaching
	 * @return PreparedStatement
	 */
	public PreparedStatement getPreparedStatement(Q query, boolean useCaching) {

		PreparedStatement pStatement = null;

		if (useCaching) {
			pStatement = cachedStatement.get();
		}

		if (pStatement == null) {
			try {
				RegularStatement stmt = getQueryGen(query).call();
				if (LOG.isDebugEnabled()) {
					LOG.debug("Query: " + stmt.getQueryString());
				}
				pStatement = sessionRef.get().prepare(stmt.getQueryString());
			} catch (Exception e) {
				throw new RuntimeException(e);
			}
		}

		if (useCaching && cachedStatement.get() == null) {
			cachedStatement.set(pStatement);
		}
		return pStatement;
	}
	
	/**
	 * Extending classes must implement this with logic for constructing the java driver query from the given Astyanax query
	 * @param query
	 * @return Callable<RegularStatement>
	 */
	public abstract Callable<RegularStatement> getQueryGen(Q query);

	/**
	 * Extending classes must implement this with logic for binding the right Astyanax query data with the pre-constructed
	 * prepared statement in the right order.
	 * @param pStatement
	 * @param query
	 * @return BoundStatement
	 */ 
	public abstract BoundStatement bindValues(PreparedStatement pStatement, Q query);
}
